package com.zzyl.nursing.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zzyl.common.constant.CacheConstants;
import com.zzyl.nursing.domain.AlertData;
import com.zzyl.nursing.domain.AlertRule;
import com.zzyl.nursing.domain.DeviceData;
import com.zzyl.nursing.mapper.AlertRuleMapper;
import com.zzyl.nursing.mapper.DeviceMapper;
import com.zzyl.nursing.service.IAlertDataService;
import com.zzyl.nursing.service.IAlertRuleService;
import com.zzyl.system.mapper.SysRoleMapper;
import com.zzyl.system.mapper.SysUserRoleMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 报警规则Service业务层处理
 * 
 * @author zzyl
 * @date 2025-10-28
 */
@Service
@Slf4j
public class AlertRuleServiceImpl extends ServiceImpl<AlertRuleMapper, AlertRule> implements IAlertRuleService
{
    @Autowired
    private AlertRuleMapper alertRuleMapper;

    /**
     * 查询报警规则
     * 
     * @param id 报警规则主键
     * @return 报警规则
     */
    @Override
    public AlertRule selectAlertRuleById(Long id)
    {
        return getById(id);
    }

    /**
     * 查询报警规则列表
     * 
     * @param alertRule 报警规则
     * @return 报警规则
     */
    @Override
    public List<AlertRule> selectAlertRuleList(AlertRule alertRule)
    {
        return alertRuleMapper.selectAlertRuleList(alertRule);
    }

    /**
     * 新增报警规则
     * 
     * @param alertRule 报警规则
     * @return 结果
     */
    @Override
    public int insertAlertRule(AlertRule alertRule)
    {
        return save(alertRule) ? 1 : 0;
    }

    /**
     * 修改报警规则
     * 
     * @param alertRule 报警规则
     * @return 结果
     */
    @Override
    public int updateAlertRule(AlertRule alertRule)
    {
        return updateById(alertRule) ? 1 : 0;
    }

    /**
     * 批量删除报警规则
     * 
     * @param ids 需要删除的报警规则主键
     * @return 结果
     */
    @Override
    public int deleteAlertRuleByIds(Long[] ids)
    {
        return removeByIds(Arrays.asList(ids)) ? 1 : 0;
    }

    /**
     * 删除报警规则信息
     * 
     * @param id 报警规则主键
     * @return 结果
     */
    @Override
    public int deleteAlertRuleById(Long id)
    {
        return removeById(id) ? 1 : 0;
    }
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private DeviceMapper deviceMapper;
    @Value("${alertRule.roleRepair}")
    private String roleRepair;//维修员角色
    @Value("${alertRule.roleAdmin}")
    private String roleAdmin;//超级管理员
    @Autowired
    private SysUserRoleMapper sysUserRoleMapper;
    @Autowired
    private SysRoleMapper sysRoleMapper;
    @Autowired
    private IAlertDataService alertDataService;

    /**
     * 过滤报警规则，将符合要求的数据存入报警数据表
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void alertRuleFilter() {
    //1.查询所有启用的报警规则
        Long count = this.lambdaQuery().eq(AlertRule::getStatus, 1).count();
        if (count == 0) {
            log.error("[报警规则校验]没有查询到任何报警规则，无需处理");
            return;
        }
    //2．查询所有设备上报的数据
        List<Object> values = redisTemplate.opsForHash().values(CacheConstants.IOT_DEVICE_LAST_DATA);
        if(CollUtil.isEmpty(values)){
            log.error("[报警规则校验]没有查询到设备上报的数据，无需处理");
            return;
        }
    //3.设备上报的数据格式化处理（转换类型为List）
        List<DeviceData> deviceDataListAll = new ArrayList<>();
        for (Object value : values) {
            String deviceDataListJSON = (String) value;
            List<DeviceData> deviceDataList = JSONUtil.toList(deviceDataListJSON, DeviceData.class);
            deviceDataListAll.addAll(deviceDataList);
        }
    //4．遍历设备上报的数据
        for (DeviceData deviceData : deviceDataListAll) {
            // 前置校验：如果设备上报数据的时间是很久之前的时间，那就忽略。
            int timeout = 60 * 60;//单位是秒，如果超过1个小时的报警数据就不再处理
            LocalDateTime alarmTime = deviceData.getAlarmTime();
            LocalDateTime now = LocalDateTime.now();
            long between = Math.abs(LocalDateTimeUtil.between(alarmTime, now, ChronoUnit.SECONDS));
            if(between>timeout){
                log.error("[报警规则校验]设备名称：{}, 功能：{}, 设备ID：{}, 数据上报时间太久，已超过{}秒，不再处理", deviceData.getDeviceName(), deviceData.getFunctionId(), deviceData.getIotId(), timeout);
                continue;
            }
            //4.1查询设备关联的报警规则数据,准备对应报警规规则数据
            //4.1.1 查询上报数据对应的设备同类型产品下全部设备关联的报警规则列表
            List<AlertRule> alertRuleListForProduct = this.lambdaQuery()
                    .eq(AlertRule::getProductKey, deviceData.getProductKey())
                    .eq(AlertRule::getFunctionId, deviceData.getFunctionId())//功能标识
                    .eq(AlertRule::getIotId, -1)
                    .eq(AlertRule::getStatus, 1)
                    .list();
            //4.1.2 查询报数据对应的设备自己关联的报警规则列表
            List<AlertRule> alertRuleListForDevice = this.lambdaQuery()
                    .eq(AlertRule::getProductKey, deviceData.getProductKey())
                    .eq(AlertRule::getFunctionId, deviceData.getFunctionId())
                    .eq(AlertRule::getIotId, deviceData.getIotId())
                    .eq(AlertRule::getStatus, 1)
                    .list();
            //4.1.3 合并规则列表且去重
            Set<AlertRule> alertRuleListAll = CollUtil.unionDistinct(alertRuleListForProduct, alertRuleListForDevice);
            if(CollUtil.isEmpty(alertRuleListAll)){
                log.error("[报警规则校验]设备名称：{}, 功能：{}, 设备ID：{}, 没有查询到设备关联的报警规则", deviceData.getDeviceName(), deviceData.getFunctionId(), deviceData.getIotId());
                continue;
            }
            //4.2遍历每一条报警规则数据
            for (AlertRule alertRule : alertRuleListAll) {
                //4.2.1第一层校验：判断是否在规则的生效时间内
                String period = alertRule.getAlertEffectivePeriod();//时间段字符串，格式如：00:00:00~23:59:59
                if (StrUtil.isBlank(period)) {
                    log.error("[报警规则校验]设备名称：{}, 功能：{}, 设备ID：{}, 规则名：{},报警规则生效时间段为空，跳过该规则"
                            , deviceData.getDeviceName(), deviceData.getFunctionId(), deviceData.getIotId(), alertRule.getAlertRuleName());
                    continue;
                }
                List<String> periodList = Arrays.asList(period.split("~"));
                LocalTime startTime = LocalTime.parse(periodList.get(0));
                LocalTime endTime = LocalTime.parse(periodList.get(1));
                LocalTime deviceAlertTime = deviceData.getAlarmTime().toLocalTime();
                if((deviceAlertTime.isBefore(startTime))||(deviceAlertTime.isAfter(endTime))){
                    log.error("[报警规则校验]设备名称：{}, 功能：{}, 设备ID：{}, 规则名：{},设备上报数据时间不在报警规则生效时间段内"
                                , deviceData.getDeviceName(), deviceData.getFunctionId(), deviceData.getIotId(), alertRule.getAlertRuleName());
                    continue;
                }

                //4.2.2第二层校验：判断数据是否达到规则的阙值 报警次数
                String countKey = CharSequenceUtil.format(CacheConstants.IOT_ALERT_COUNT_KEY,
                        deviceData.getIotId(), deviceData.getFunctionId(), alertRule.getId());
                String operator = alertRule.getOperator();//比较运算符的符合
                double alertValue = (double)alertRule.getValue();//阈值
                //Double.valueOf() 将字符串转为Double对象.doubleValue() 将Double对象转为基本类型double
                double alarmValue = Double.valueOf(deviceData.getDataValue()).doubleValue();//设备上报的数据
                if((operator.equals("<") && alarmValue>=alertValue)||(operator.equals(">=") && alarmValue<alertValue)){
                    log.error("[报警规则校验]设备名称：{}, 功能：{}, 设备ID：{}, 规则名：{}, 现在值：{}, 未到到告警规则阈值：{}"
                            , deviceData.getDeviceName(), deviceData.getFunctionId(), deviceData.getIotId(), alertRule.getAlertRuleName(), alarmValue, alertValue);
                    //删除持续周期报警次数缓存数据
                    redisTemplate.delete(countKey);
                    continue;
                }
                //4.2.3第三层校验：判断沉默周期
                String silentKey = CharSequenceUtil.format(CacheConstants.IOT_ALERT_SILENT_KEY
                        , deviceData.getIotId(), deviceData.getFunctionId(), alertRule.getId());
                String silentValue = redisTemplate.opsForValue().get(silentKey);
                if(StrUtil.isNotBlank(silentValue)){
                    Long ttl = redisTemplate.getExpire(silentKey);
                    log.error("[报警规则校验]设备名称：{}, 功能：{}, 设备ID：{}, 规则名：{}, 还在沉默周期内，无需处理，沉默周期还剩余{}秒 "
                            , deviceData.getDeviceName(), deviceData.getFunctionId(), deviceData.getIotId(), alertRule.getAlertRuleName(), ttl );
                    continue;
                }
                //4.2.4第四层校验：判断持续周期
                Integer duration = alertRule.getDuration();//持续周期上限值
                //.increment原子性自增：对 countKey 对应的值执行 +1 操作（线程安全）
                Long durationCount = redisTemplate.opsForValue().increment(countKey);
                if(durationCount<duration){
                    log.error("[报警规则校验]设备名称：{}, 功能：{}, 设备ID：{}, 规则名：{}, 还在持续周期内（未达上限），现在次数：{}，上限次数：{} "
                            , deviceData.getDeviceName(), deviceData.getFunctionId(), deviceData.getIotId(), alertRule.getAlertRuleName(), durationCount, duration);
                    continue;
                }
                //报警沉默周期缓存数据
                Integer alertSilentPeriod = alertRule.getAlertSilentPeriod();//沉默周期的时间值（单位是分钟）
                redisTemplate.opsForValue().set(silentKey,"aa",alertSilentPeriod, TimeUnit.MINUTES);
                //删除持续周期报警次数缓存数据
                redisTemplate.delete(countKey);
                //4.2.5报警规则校验通过，则处理数据完毕后新增报警数据
                //4.2.5.1查询护理员或维修员ID列表
                List<Long> nursingIdList = null;
                //如果是老人数据
                if(alertRule.getAlertDataType()==0) {
                    //如果是移动设备（随身设备），通过老人关联查询护理人员ID列表
                    if(deviceData.getLocationType()==0) {
                        nursingIdList = deviceMapper.selectNuringIdsByMoveIotId(deviceData.getIotId());
                    } else {
                        //如果是固定设备，通过床位关联查询护理人员ID列表
                        nursingIdList = deviceMapper.selectNuringIdsByStopIotId(deviceData.getIotId());
                    }
                } else {
                    //如果是设备数据，则只查询维修员ID列表
                    nursingIdList = sysUserRoleMapper.selectUserIdsByRoleName(roleRepair);
                }
                //4.2.5.2查询管理员ID列表
                List<Long> adminUserIdList = sysUserRoleMapper.selectUserIdsByRoleName(roleAdmin);
                //4.2.5.2.1合并用户ID列表
                Set<Long> userIdList = CollUtil.unionDistinct(nursingIdList, adminUserIdList);
                if(CollUtil.isEmpty(userIdList)){
                    log.error("[报警规则校验]没有查询到任何用户ID，设备ID：{}", deviceData.getIotId());
                    continue;
                }
                //4.2.5.3构建报警数据
                //构建基础报警数据对象
                AlertData baseData = BeanUtil.toBean(deviceData, AlertData.class);
                baseData.setId(null);//主键自增给个空不报错
                baseData.setAlertRuleId(alertRule.getId());//报警规则ID
                baseData.setStatus(0);//待处理
                baseData.setType(alertRule.getAlertDataType());
                //报警原因，格式：功能名称+运算符+阈值+持续周期+沉默周期
                String alertReasonTemplate = "功能：{}{}{}，持续周期：{}，沉默周期：{}";
                String alertReason = CharSequenceUtil.format(alertReasonTemplate, alertRule.getFunctionName()
                        , alertRule.getOperator(), alertRule.getValue(), alertRule.getDuration(), alertRule.getAlertRuleName());
                baseData.setAlertReason(alertReason);
                //遍历构建每个用户（谁来处理，就写对应用户ID）的报警数据
                List<AlertData> alertDataList = userIdList.stream().map(x -> {
                    AlertData alertData = BeanUtil.toBean(baseData, AlertData.class);
                    alertData.setUserId(x);
                    return alertData;
                }).collect(Collectors.toList());
                //4.2.5.4 批量新增报警数据
                alertDataService.saveBatch(alertDataList);
                log.info("[报警规则校验]新增报警数据完毕。设备ID：{}，报警信息：{}", deviceData.getIotId(), alertReason);
            }
        }
    }
}
